package org.mmtk.policy.compressor;

import org.mmtk.utility.Constants;
import org.mmtk.utility.Log;
import org.mmtk.utility.alloc.Allocator;
import org.mmtk.vm.VM;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Extent;
import org.vmmagic.unboxed.ObjectReference;

// TODO: decide what to do with objects which span two pages
@Uninterruptible
public class MetadataComputingScanner extends org.mmtk.utility.alloc.LinearScan {

  protected static final int HANDLED = 1;
  protected static final int UNHANDLED = 2;
  protected static final int BUSY = 3;
  
  private boolean VERBOSE = true;
  
  
  private int totalLiveData;
  private int sizeInPage;
  private int pages;
  private boolean firstInBlock;
  
  public MetadataComputingScanner() {
    totalLiveData = 0;
    sizeInPage = 0;
    pages = 0;
    firstInBlock = true;
  }
  
  public void newRegion(Address region) {
    if(VERBOSE) {
      Log.write("Starting scan of region "); Log.write(region); Log.writeln();
    }
    clearFirstInBlock();
  }
  
  @Override
  public void scan(ObjectReference object) {
    
    if (CompressorMetadata.liveBitSet(object)) {
      if (VERBOSE) {
        Log.write(object); Log.write(" is Alive, computing metadata"); Log.writeln();
      }

      if (markFirstInBlock()) {
        if (VERBOSE) {
          Log.write("firstInBlock "); Log.writeln();
          Log.write("Object "); Log.write(object); Log.write(" Offset "); Log.write(totalLiveData);
          Log.writeln();
        }
        
        setOffset(totalLiveData, object);
      }
      
      int size = VM.objectModel.getSizeWhenCopied(object);
      int align = VM.objectModel.getAlignWhenCopied(object);
      int offset = VM.objectModel.getAlignOffsetWhenCopied(object);
      
      if(VERBOSE) {
        Log.write("[ComputingScanner] "); Log.write("size = "); Log.write(size); Log.writeln();
        Log.write("[ComputingScanner] "); Log.write("align = "); Log.write(align); Log.writeln();
        Log.write("[ComputingScanner] "); Log.write("offset = "); Log.write(offset); Log.writeln();
      }
      
      if(VERBOSE) {
        Log.write("[ComputingScanner] "); 
        Log.write("totalLiveData before align = "); Log.write(totalLiveData); Log.writeln();
      }
          
      // Set the cursor on the aligned start of the next object
      totalLiveData = Allocator.alignAllocationNoFill(Address.zero().plus(totalLiveData), 
          align, offset).toInt();

      if(VERBOSE) {
        Log.write("[ComputingScanner] "); 
        Log.write("totalLiveData after align = "); Log.write(totalLiveData); Log.writeln();
      }
      
      sizeInPage += size;
      if(sizeInPage > Constants.BYTES_IN_PAGE) {
        if(VERBOSE) {
          Log.write("setFirstObject "); Log.write(object); Log.writeln();
        }
        
        setFirstObject(object);
        pages++;
        sizeInPage = sizeInPage - Constants.BYTES_IN_PAGE;  
      }
      
      totalLiveData += size;
    }
    else {
      if (VERBOSE) {
        Log.write(object); Log.write(" is Dead"); Log.writeln();
      }
    }
  }
  
  /**
   * Returns true iff object 'object' is first in its block
   * @param object the object to check
   * @return true iff object 'object' is first in its block
   */
  private boolean markFirstInBlock() {
    boolean ret = firstInBlock;
    firstInBlock = false;
    return ret;
  }
  
  private void clearFirstInBlock() {
    firstInBlock = true;
  }
  
  public void setOffset(int offset, ObjectReference obj){
    Address offsetCell = CompressorMetadata.getOffsetVectorCellAddress(obj.toAddress());
    if(VERBOSE) {
      Log.write("Obj["); Log.write(obj); Log.write("]"); 
      Log.write(" offsetCell "); Log.write(offsetCell);
      Log.write(" offset "); Log.write(offset);
      Log.writeln();
    }
    
    offsetCell.store(offset);
  }

  public void setFirstObject(ObjectReference obj) {
    Address firstObjCell = CompressorMetadata.getFirstObjCellAddress(obj.toAddress());
    firstObjCell.store(obj.toAddress());
  }
  
  public void setStatus(Address page, int status) {
    if (VM.VERIFY_ASSERTIONS) {
      VM.assertions._assert(status >= HANDLED && status <= BUSY);
    }
    Address offsetCell = CompressorMetadata.getStatusVectorCellAddress(page);
    offsetCell.store(status);
  }
  
}
